/*
 * =====================================================================================
 *
 *       Filename:  timer_wheel.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  05/21/2011 03:18:06 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Ken Chen (Ken), chen_kenz@emc.com
 *        Company:  EMC
 *
 * =====================================================================================
 */

#include <assert.h>
#include "pthread.h"
#include "timer_wheel.h"

#define BASE_TIER_SIZE (1 << 8)
#define OTHER_TIER_SIZE (1 << 6)
#define BASE_TIER_MASK (BASE_TIER_SIZE - 1)
#define OTHER_TIER_MASK (OTHER_TIER_SIZE - 1)
#define TIMER_GRANULARITY 100

struct timevec_base
{
  struct timer_entry_list vec[BASE_TIER_SIZE];
};

struct timer_wheel_base 
{
  pthread_mutex_t tim_lock;
  struct timevec_base tv1; 
  unsigned int cur_idx;
};

/* Not thread safe, assume the lock is already held */
static void inline do_add_timer(struct timer_wheel_base* timbase, 
                                struct timer_entry* tim)
{
  assert(tim->expired_interval < BASE_TIER_SIZE * TIMER_GRANULARITY);
  struct timer_entry_list* tlist = NULL;
  struct timer_entry_list* new_entry = (struct timer_entry_list*)tim;
  unsigned int idx = (tim->expired_interval / TIMER_GRANULARITY) + timbase->cur_idx; 
  /*printf("do_add_timer_entry: %u %u\n", tim->expired_interval / 100, (idx & BASE_TIER_MASK));*/
  tlist = &timbase->tv1.vec[idx & BASE_TIER_MASK];
  /* add to the tail of the timer list */
  tlist->prev->next = new_entry;
  new_entry->prev = tlist->prev;
  new_entry->next = tlist;
  tlist->prev = new_entry;
  tim->pending = 1;
}

/* Not thread safe, assume the lock is already held */
static void inline do_delete_timer(struct timer_entry* tim)
{
  struct timer_entry_list* deleted_entry = (struct timer_entry_list*)tim;
  deleted_entry->prev->next = deleted_entry->next;
  deleted_entry->next->prev = deleted_entry->prev;
  deleted_entry->prev = NULL;
  deleted_entry->next = NULL;
  tim->pending = 0;
}

/* Return 1 if the timer entry inited, otherwise return 0 */
static int inline timer_inited(struct timer_entry* tim)
{
  return ((tim != NULL) && (tim->base != NULL));
}

/* Return 1 if the timer is pending(active), otherwise return 0 */
static int inline timer_pending(const struct timer_entry* tim)
{
  return tim->pending == 1; 
}

/* The system has only one instance of timer wheel */
static struct timer_wheel_base sys_timer_base; 

/* Called when system is inited */
void init_timer_system(void)
{
  int i = 0;
  pthread_mutex_init(&sys_timer_base.tim_lock, NULL);
  for(; i < BASE_TIER_SIZE; ++i)
  {
    sys_timer_base.tv1.vec[i].prev = &sys_timer_base.tv1.vec[i];
    sys_timer_base.tv1.vec[i].next = &sys_timer_base.tv1.vec[i];
  }
  sys_timer_base.cur_idx = 0;
}

/* Note: Timed out call back function should be quick,
 * otherwise it will impact the precise of the timer, and
 * this init func must be called before calling add_timer
 */
void init_timer(struct timer_entry* tim, 
                timer_callback func, 
                void* data, 
                unsigned long interval) 
{
  assert(tim != NULL);
  tim->pending_timers.prev = NULL;
  tim->pending_timers.next = NULL;
  tim->expired_interval = interval;
  tim->base = &sys_timer_base;
  tim->callback = func;
  tim->data = data;
  tim->pending = 0;
}

/* Start a timer */
int add_timer(struct timer_entry* tim) 
{
  assert(timer_inited(tim));
  pthread_mutex_lock(&tim->base->tim_lock);
  do_add_timer(tim->base, tim);
  pthread_mutex_unlock(&tim->base->tim_lock);

  return 0;
}

/* Deactivated a timer, return 1 if the timer is pending(active),
 * otherwise return 0 (the timer is inactive, already processed) */
int delete_timer(struct timer_entry* tim) 
{
  int ret = 0;
  assert(timer_inited(tim));
  /* double check locking */
  if(timer_pending(tim))
  {
    pthread_mutex_lock(&tim->base->tim_lock);
    if(timer_pending(tim))
    {
      do_delete_timer(tim);
      ret = 1;
    }
    pthread_mutex_unlock(&tim->base->tim_lock);
  }

  return ret;
}

/* Check the time wheel every 100 millisecond.
 * The granularity of this timer implementaion
 * is 100 millisecond.
 */
int schedule_timers(void)
{
  struct timer_entry_list* tlist;
  struct timer_entry_list* next_entry;

  for(;;)
  {
    pthread_mutex_lock(&sys_timer_base.tim_lock);
    tlist = &sys_timer_base.tv1.vec[sys_timer_base.cur_idx]; 
    for(; tlist->next != tlist; )
    {
      /*printf("schedule_timer: %u\n", sys_timer_base.cur_idx);*/
      struct timer_entry* expired_entry = (struct timer_entry*)(tlist->next);
      timer_callback callfunc = expired_entry->callback; 
      void* data = expired_entry->data;
      do_delete_timer(expired_entry); 
      pthread_mutex_unlock(&sys_timer_base.tim_lock); 
      callfunc(data);
      pthread_mutex_lock(&sys_timer_base.tim_lock);
    }
    sys_timer_base.cur_idx = (++sys_timer_base.cur_idx) & BASE_TIER_MASK;
    pthread_mutex_unlock(&sys_timer_base.tim_lock);
    usleep(TIMER_GRANULARITY*1000);
  }

  return 0;
}

